View invalidate的源码分析

我们在自定义View时,通常使用invalidate方法来刷新View,本篇将对invalidate的实现进行分析。invalidate有多个重载方法,
但其最终的实现都是类似的,这里我们从invalidate()开始分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
frameworks/base/core/java/android/view/View.java
/**
* Invalidate the whole view. If the view is visible,
* {@link #onDraw(android.graphics.Canvas)} will be called at some point in
* the future. This must be called from a UI thread. To call from a non-UI thread,
* call {@link #postInvalidate()}.
*/
//使整个view无效,将会导致view的onDraw调用 即重绘view的过程
public void invalidate() {
invalidate(true);//true表示刷新缓存也应该失效
}

//true表示刷新缓存也应该失效,如果置位false表示view的内容或者大小没有发生变化
void invalidate(boolean invalidateCache) {
if (skipInvalidate()) {//是否应该跳出本次过程,跳过绘制的条件需满足:view不可见 且没有当前动画在执行
return;
}
/*
* 可以进行刷新的条件:
* 1: 需要绘制且已经设置过边界
* 2:当前绘制缓存还是有效,但要使其失效
* 3:view将处于invalided状态
* 4: 透明度发生了变化
* */
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) ||
(invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) ||
(mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED || isOpaque() != mLastIsOpaque) {
mLastIsOpaque = isOpaque();
mPrivateFlags &= ~PFLAG_DRAWN;//重置绘制标记
mPrivateFlags |= PFLAG_DIRTY;//标记PFLAG_DIRTY 表示view当前已经失效
if (invalidateCache) {//需要失效缓存
mPrivateFlags |= PFLAG_INVALIDATED;//标记失效
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;//清除缓存有效的标记
}
final AttachInfo ai = mAttachInfo;//取到view的attch信息
final ViewParent p = mParent;//父view
//noinspection PointlessBooleanExpression,ConstantConditions
if (!HardwareRenderer.RENDER_DIRTY_REGIONS) {
if (p != null && ai != null && ai.mHardwareAccelerated) {//开启了硬件加速
// fast-track for GL-enabled applications; just invalidate the whole hierarchy
// with a null dirty rect, which tells the ViewAncestor to redraw everything
p.invalidateChild(this, null);//开启硬件加速了就将view所有的区域标记为脏区域以使viewRoot重绘所有的内容
return;
}
}
//未开启硬件加速
if (p != null && ai != null) {
final Rect r = ai.mTmpInvalRect;
r.set(0, 0, mRight - mLeft, mBottom - mTop);//将Rect设置为view的大小
// Don't call invalidate -- we don't want to internally scroll
// our own bounds
p.invalidateChild(this, r);//调用父view的invalidateChild方法 具体见ViewGroup
}
}
}

invalidate的实现很简单,只需要满足一定的条件,就需要走绘制流程,这里需要注意两个标记一个是PFLAG_INVALIDATED,代表了View当前已经失效了,
另一个PFLAG_DRAWING_CACHE_VALID,表示View的绘制缓存有效。这里根据参数invalidateCache为true对其进行设置,对于硬件加速和非硬件加速的情况分别走
不同的流程,现在大多数其实是走硬件加速的流程,但这里我们还是看软件绘制的流程,关于硬件加速我们在别的篇章中介绍,在这里Mark~一下就好。

这里会设置一个Rect区域,这个区域实际上就是我们View的大小,也是待刷新的dirty区域,因为mRight-mLeft是View的宽度,mBottom-mTop就是View的高度,随后通过调用ViewParent的
invalidateChild方法,这里的ViewParent实际上就是当前view的父View或者view层级数的ViewRoot,所以invalidateChild是会走到ViewGroup或者ViewRootImpl
中去的。我们接着看哈~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public final void invalidateChild(View child, final Rect dirty) {
ViewParent parent = this;

final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION)
== PFLAG_DRAW_ANIMATION;//child view是否在执行动画
Matrix childMatrix = child.getMatrix();
final boolean isOpaque = child.isOpaque() && !drawAnimation &&
child.getAnimation() == null && childMatrix.isIdentity();//发起请求的view是否是不透明的,执行动画的view则不认为它不透明
int opaqueFlag = isOpaque ? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY;//child view 的 dirty标记

……
//存放dirty region的起点坐标
final int[] location = attachInfo.mInvalidateChildLocation;
location[CHILD_LEFT_INDEX] = child.mLeft;
location[CHILD_TOP_INDEX] = child.mTop;
……

do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}

if (drawAnimation) {
if (view != null) {
view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
} else if (parent instanceof ViewRootImpl) {
((ViewRootImpl) parent).mIsAnimating = true;
}
}

// If the parent is dirty opaque or not dirty, mark it dirty with the opaque
// flag coming from the child that initiated the invalidate
if (view != null) {
if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
view.getSolidColor() == 0) {
opaqueFlag = PFLAG_DIRTY;
}
if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;
}
}
//得到的是父view的父view
parent = parent.invalidateChildInParent(location, dirty);
……
} while (parent != null);
}
}

invalidateChild接收两个参数,发起invalidate的child view即dirty区域,dirty区域的起始坐标点就是child view
在父view中的左上角坐标,随后进行do…while循环,在循环中我们忽略执行动画的情况,然后为当前父view设置dirty标记。
这个dirty标记为父view记录下子view的视图情况,后续在draw时会用到该标记判断是否需要绘制通过onDraw绘制view
随后调用invalidateChildInParent方法并返回当前父View的父View,一直到ViewRootImpl。可见这个循环其实是一个在View
层级数中从发起View的父View不断上溯到ViewRoot执行invalidateChildInParent的过程,那么在这之间,invalidateChildInParent
到底会做些什么事情呢?这里我们传递给invalidateChildInParent的是dirty区域的在当前父view的左上角的坐标和dirty区域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// /frameworks/base/core/java/android/view/ViewGroup.java
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||
(mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {
if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=
FLAG_OPTIMIZE_INVALIDATE) {
//计算dirty区域在当前父view中的偏移位置
dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
location[CHILD_TOP_INDEX] - mScrollY);
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
}

final int left = mLeft;
final int top = mTop;

if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) {
dirty.setEmpty();
}
}
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;//同时置ViewParent的PFLAG_DRAWING_CACHE_VALID标记为0

//更新location
location[CHILD_LEFT_INDEX] = left;
location[CHILD_TOP_INDEX] = top;

if (mLayerType != LAYER_TYPE_NONE) {
mPrivateFlags |= PFLAG_INVALIDATED;
mLocalDirtyRect.union(dirty);
}
return mParent;

} else {
mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID;
location[CHILD_LEFT_INDEX] = mLeft;
location[CHILD_TOP_INDEX] = mTop;
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
dirty.set(0, 0, mRight - mLeft, mBottom - mTop);
} else {
// in case the dirty rect extends outside the bounds of this container
dirty.union(0, 0, mRight - mLeft, mBottom - mTop);//和当前VeiwGroup所属区域做并集
}

if (mLayerType != LAYER_TYPE_NONE) {
mPrivateFlags |= PFLAG_INVALIDATED;//ViewGroup的标记置失效
mLocalDirtyRect.union(dirty);
}

return mParent;
}
}

return null;
}

在invalidateChildInParent中,首先通过offset计算dirty区域在当前父view中偏移位置,起始的dirty区域就是发起invalidate的子view
它再父view中的左上角位置就是(mLeft,mTop),随后计算dirty区域,首先判断FLAG_CLIP_CHILDREN是否设置,即我们在布局文件中
设置的android:clipChildren,默认是设置为true的,这个属性是用来限制子view是否在父view的绘制区域内的。设置为false即FLAG_CLIP_CHILDREN未设置的情况下
表示可以超出父view的绘制区域。这种情况下的话就设置dirty区域为当前父view的区域。否则FLAG_CLIP_CHILDREN设置就根据当前dirty区域和父view的区域做交集运算后
得到的dirty区域。如果没交集则dirty置空。然后更新location为当前父view在其父view中的左上角位置,为下一次计算脏区域在其父view中的偏移做准备。这样经过一层层的
计算后最终回溯到ViewRootImpl中的invalidateChildInParent中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// /frameworks/base/core/java/android/view/ViewRootImpl.java
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
if (dirty == null) {//脏区域为null说明要重新绘制整个view树
invalidate();
return null;
} else if (dirty.isEmpty() && !mIsAnimating) {//没有动画在执行且没有脏区域就不用绘制了
return null;
}

if (mCurScrollY != 0 || mTranslator != null) {
mTempRect.set(dirty);
dirty = mTempRect;
if (mCurScrollY != 0) {
dirty.offset(0, -mCurScrollY);
}
if (mTranslator != null) {
mTranslator.translateRectInAppWindowToScreen(dirty);
}
if (mAttachInfo.mScalingRequired) {
dirty.inset(-1, -1);
}
}

final Rect localDirty = mDirty;
if (!localDirty.isEmpty() && !localDirty.contains(dirty)) {
mAttachInfo.mSetIgnoreDirtyState = true;
mAttachInfo.mIgnoreDirtyState = true;
}

// Add the new dirty rect to the current one
localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
// Intersect with the bounds of the window to skip
// updates that lie outside of the visible region
final float appScale = mAttachInfo.mApplicationScale;
final boolean intersected = localDirty.intersect(0, 0,
(int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
if (!intersected) {
localDirty.setEmpty();
}

/*ViewRootImpl同样是ViewParent,它和ViewGroup的invalideChildInParent的区别在于
* ViewRootImpl在统计完dirty region后会进行schedualTraversals进行绘制流程*/
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
scheduleTraversals();
}

return null;
}

checkThread会检查更新的线程是否是ui线程,如果dirty区域为null说明需要绘制整个view树,如果dirty区域为空或者未执行动画也不需要
再进行下去了。如果mCurScrollY不为空说明页面有滚动过,需要据此重新计算dirty区域。随后将dirty区域添加到localDrity中即当前view树
中的dirty区域中去。接着讲当前dirty区域和整个页面区域做交集计算,intersected一般为true也就是有交集,最后通过scheduleTraversals
进行重绘的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// /frameworks/base/core/java/android/view/ViewRootImpl.java
private void performDraw() {
boolean fullRedrawNeeded = mFullRedrawNeeded;
mFullRedrawNeeded = false;
……
try {
draw(fullRedrawNeeded, updateTranformHint);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
……
}

private void draw(boolean fullRedrawNeeded, boolean updateTranformHint) {
Surface surface = mSurface;
if (!surface.isValid()) {//surface无效的话就返回
return;
}
……
final Rect dirty = mDirty;
//如果是绘制整个页面 就设置dirty为整个屏幕的大小
if (fullRedrawNeeded) {
attachInfo.mIgnoreDirtyState = true;
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
……
//重绘区域不为空 或者正在执行动画
if (!dirty.isEmpty() || mIsAnimating) {
//开启了硬件加速
if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) {
……
}else{
……
//软件绘制 dirty描述绘制区域
if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {
return;
}
}

}
}

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff,
boolean scalingRequired, Rect dirty) {
Canvas canvas;
try {
int left = dirty.left;
int top = dirty.top;
int right = dirty.right;
int bottom = dirty.bottom;
……
//根据重绘区域获取一个Canvas
canvas = mSurface.lockCanvas(dirty);

}catch(...){
……
}

try{
mView.draw(canvas);//view的绘制
}finally{
……
surface.unlockCanvasAndPost(canvas);
……
}
return true;
}

scheduleTraversals通过performDraw来绘制view,这里实际上是调用draw(boolean fullRedrawNeeded, boolean updateTranformHint)方法,
对应非硬件加速的情况,这个方法内部调用drawSoftware来进行绘制,注意这里的dirty实际上就是当前view树计算后得到的脏区域。当然也包括了我们之前
调用view的invalidate后计算的脏区域。通过这个脏区域通过在mSurface中设置一个裁剪区域并返回一个Canvas,随后的绘制就在此Canvas的裁剪区域中进行绘制。

View的draw绘制过程主要包括以下方面:

  1. Draw the background
  2. If necessary, save the canvas’ layers to prepare for fading
  3. Draw view’s content
  4. Draw children
  5. If necessary, draw the fading edges and restore layers
  6. Draw decorations (scrollbars for instance)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void draw(Canvas canvas) {
……
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); //子view是否不透明且未执行动画
……
background.draw(canvas);

if (!dirtyOpaque) onDraw(canvas);//子视图不透明 会覆盖掉当前视图 所以skip

dispatchDraw(canvas);

onDrawScrollBars(canvas);
……
}

在draw流程中是否回调onDraw是由dirtyOpaque决定的,而dirtyOpaque是根据标记PFLAG_DIRTY_OPAQUE是否设置来决定的,
还记得在上面invalidateChild时会为父view设置这个标记?这个标记表明子view是不透明的且没有在执行动画,那么此时
就没必要对view进行绘制了,因为子view是在父view之上的,会覆盖掉当前view的视图,所有就没有必要绘制了。
需要注意的是,ViewGroup作为一个容器控件,默认情况下是没有任何东西可画的,它是一个透明控件。

draw过程中的dispatchDraw用来绘制子view,我们看下ViewGroup中实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// /frameworks/base/core/java/android/view/ViewGroup.java
@Override
protected void dispatchDraw(Canvas canvas) {//绘制子视图
final int count = mChildrenCount;
final View[] children = mChildren;

……
if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
for (int i = 0; i < count; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {//子视图可见或者设置了动画
more |= drawChild(canvas, child, drawingTime);//绘制子视图
}
}
} else {
for (int i = 0; i < count; i++) {
final View child = children[getChildDrawingOrder(count, i)];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
}
……
}

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}

drawChild会调用view的另一个重载方法,它有三个参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// /frameworks/base/core/java/android/view/View.java
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
……
mPrivateFlags |= PFLAG_DRAWN;
/*quickReject判断当前View的区域是否落在Canvas的裁剪区域之外,如果是返回true,表示跳过当前view的绘制*/
if (!concatMatrix &&
(flags & (ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS |
ViewGroup.FLAG_CLIP_CHILDREN)) == ViewGroup.FLAG_CLIP_CHILDREN &&
canvas.quickReject(mLeft, mTop, mRight, mBottom, Canvas.EdgeType.BW) &&
(mPrivateFlags & PFLAG_DRAW_ANIMATION) == 0) {

/* 1. view没有关联的matrix矩阵
* 2. view的绘制区域落在Canvas裁剪区域之外
* 3. clipchild设置为true
* 4. 没有执行动画
* **/
mPrivateFlags2 |= PFLAG2_VIEW_QUICK_REJECTED;
return more;
}
……
if (!layerRendered) {
if (!hasDisplayList) {
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);//绘制子视图
} else {
draw(canvas);//绘制本身及子视图
}
} else {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
((HardwareCanvas) canvas).drawDisplayList(displayList, null, flags);
}
}
……
}

从draw方法中可以看出,子view的并不是每次进行绘制流程时候都需要绘制一遍,尤其是当view通过invalidate触发绘制时,
因此此时Canvas根据dirty区域设置了裁剪区域,而ViewGroup在绘制子view时会判断其区域是否落在这个裁剪区域内,如果
不在就没有必要进行绘制了,直接返回。这是通过canvas的quickReject进行判断的.随后就是字view的绘制,这里面会判断是否
设置了PFLAG_SKIP_DRAW,这个标记用来控制是否需要对View进行绘制,对ViewGroup来说默认是设置了的,所以它直接通过
dispatchDraw来绘制子view,并不会对自身进行绘制,onDraw也不会进行调用。如果想要使它绘制可以通过setWillNotDraw(false)
来清除PFLAG_SKIP_DRAW标记。这样会进入view(ViewGroup)的draw流程,但具体能不能调用onDraw还要做以下判断

final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); //子view是否不透明且未执行动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}

void setFlags(int flags, int mask) {
……
if ((changed & DRAW_MASK) != 0) {
if ((mViewFlags & WILL_NOT_DRAW) != 0) {
if (mBackground != null) {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
mPrivateFlags |= PFLAG_ONLY_DRAWS_BACKGROUND;
} else {
mPrivateFlags |= PFLAG_SKIP_DRAW;
}
} else {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
}
requestLayout();
invalidate(true);//通过invalidate刷新
}
……
}

到这里invalidate的流程就分析完了,需要注意的是,invalidate会触发绘制流程,但是并不会触发onMeasure和onLayout。

坚持原创技术分享,您的支持将鼓励我继续创作!